using System;
using System.Collections;
using System.Collections.Generic;
using static LightCAD.MathLib.MathEx;

namespace LightCAD.Three
{
    public class SkinnedMesh : Mesh, IBounding
    {
        #region scope properties or methods
        //private static Vector3 _basePosition = new Vector3();
        //private static Vector4 _skinIndex = new Vector4();
        //private static Vector4 _skinWeight = new Vector4();
        //private static Vector3 _vector3 = new Vector3();
        //private static Matrix4 _matrix4 = new Matrix4();
        //private static Vector3 _vertex = new Vector3();
        private SkinnedMeshContext getContext()
        {
            return ThreeThreadContext.GetCurrThreadContext().SkinnedMeshCtx;
        }
        #endregion

        #region Properties

        public string bindMode;
        public Matrix4 bindMatrix;
        public Matrix4 bindMatrixInverse;
        public Skeleton skeleton;
        public Box3 boundingBox;
        public Sphere boundingSphere;
        #endregion

        #region constructor
        public SkinnedMesh(BufferGeometry geometry, params Material[] material) : base(geometry, material)
        {
            this.type = "SkinnedMesh";
            this.bindMode = "attached";
            this.bindMatrix = new Matrix4();
            this.bindMatrixInverse = new Matrix4();
            this.boundingBox = null;
            this.boundingSphere = null;
        }
        #endregion

        #region methods

        public void computeBoundingBox()
        {

            var geometry = this.geometry;

            if (this.boundingBox == null)
            {

                this.boundingBox = new Box3();

            }

            this.boundingBox.MakeEmpty();

            var positionAttribute = geometry.getAttribute("position");
            var _vertex = getContext()._vertex;
            for (var i = 0; i < positionAttribute.count; i++)
            {

                _vertex.FromBufferAttribute(positionAttribute, i);
                this.applyBoneTransform(i, _vertex);
                this.boundingBox.ExpandByPoint(_vertex);

            }

        }

        public void computeBoundingSphere()
        {

            var geometry = this.geometry;

            if (this.boundingSphere == null)
            {

                this.boundingSphere = new Sphere();

            }

            this.boundingSphere.MakeEmpty();

            var positionAttribute = geometry.getAttribute("position");
            var _vertex = getContext()._vertex;
            for (var i = 0; i < positionAttribute.count; i++)
            {

                _vertex.FromBufferAttribute(positionAttribute, i);
                this.applyBoneTransform(i, _vertex);
                this.boundingSphere.ExpandByPoint(_vertex);

            }

        }

        public SkinnedMesh copy(SkinnedMesh source, bool recursive)
        {
            base.copy(source, recursive);
            this.bindMode = source.bindMode;
            this.bindMatrix.Copy(source.bindMatrix);
            this.bindMatrixInverse.Copy(source.bindMatrixInverse);
            this.skeleton = source.skeleton;
            return this;
        }
        public override Object3D copy(Object3D source, bool recursive = true)
        {
            return copy(source as SkinnedMesh, recursive);
        }
        public override Object3D clone(bool recursive = true)
        {
            return new SkinnedMesh(null, null).copy(this, recursive);
        }
        public void bind(Skeleton skeleton, Matrix4 bindMatrix = null)
        {
            this.skeleton = skeleton;
            if (bindMatrix == null)
            {
                this.updateMatrixWorld(true);
                this.skeleton.calculateInverses();
                bindMatrix = this.matrixWorld;
            }
            this.bindMatrix.Copy(bindMatrix);
            this.bindMatrixInverse.Copy(bindMatrix).Invert();
        }
        public void pose()
        {
            this.skeleton.pose();
        }
        public void normalizeSkinWeights()
        {
            var vector = new Vector4();
            var skinWeight = this.geometry.attributes["skinWeight"];
            for (int i = 0, l = skinWeight.count; i < l; i++)
            {
                vector.FromBufferAttribute(skinWeight, i);
                var scale = 1.0 / vector.ManhattanLength();
                if (scale != Infinity)
                {
                    vector.MultiplyScalar(scale);
                }
                else
                {
                    vector.Set(1, 0, 0, 0); // do something reasonable
                }
                skinWeight.setXYZW(i, vector.X, vector.Y, vector.Z, vector.W);
            }
        }
        public override void updateMatrixWorld(bool force)
        {
            base.updateMatrixWorld(force);
            if (this.bindMode == "attached")
            {
                this.bindMatrixInverse.Copy(this.matrixWorld).Invert();
            }
            else if (this.bindMode == "detached")
            {
                this.bindMatrixInverse.Copy(this.bindMatrix).Invert();
            }
            else
            {
                console.warn("THREE.SkinnedMesh: Unrecognized bindMode: " + this.bindMode);
            }
        }
        public Vector3 applyBoneTransform(int index, Vector3 vector)
        {
            var ctx = getContext();
            var _skinIndex = ctx._skinIndex;
            var _skinWeight = ctx._skinWeight;
            var _basePosition = ctx._basePosition;
            var _matrix4 = ctx._matrix4;
            var _vector3 = ctx._vector3;

            var skeleton = this.skeleton;
            var geometry = this.geometry;
            _skinIndex.FromBufferAttribute(geometry.attributes["skinIndex"], index);
            _skinWeight.FromBufferAttribute(geometry.attributes["skinWeight"], index);

            _basePosition.Copy(vector).ApplyMatrix4(this.bindMatrix);
            vector.Set(0, 0, 0);

            for (int i = 0; i < 4; i++)
            {
                var weight = _skinWeight.GetComponent(i);
                if (weight != 0)
                {
                    var boneIndex = (int)_skinIndex.GetComponent(i);
                    _matrix4.MultiplyMatrices(skeleton.bones[boneIndex].matrixWorld, skeleton.boneInverses[boneIndex]);

                    vector.AddScaledVector(_vector3.Copy(_basePosition).ApplyMatrix4(_matrix4), weight);
                }
            }
            return vector.ApplyMatrix4(this.bindMatrixInverse);
        }

        public Vector3 boneTransform(int index, Vector3 vector)
        { // @deprecated, r151

            console.warn("THREE.SkinnedMesh: .boneTransform() was renamed to .applyBoneTransform() in r151.");
            return this.applyBoneTransform(index, vector);

        }

        #endregion
    }
}
